#include "stdafx.h"
#include "SignatureControl.h"

typedef struct __SignatureState
{
	int colour;		// Current pen colour
	int width;		// Current pen width
	HBITMAP hbmp;	// Offscreen bitmap buffer
	HDC hdc;		// Drawing Context for offscreen bitmap
	POINT pt;		// Last point stylus was seen at
} SignatureState;

// Save the bitmap 'hbmpSource' into the file specified by 'pszFilename'
static void SaveBitmap(LPCTSTR pszFilename, HDC hdcSource, HBITMAP hbmpSource)
{
	// Determine the size of the source bitmap
	BITMAP bm;
	GetObject(hbmpSource, sizeof(BITMAP), &bm);

	// Create a device independant bitmap (DIB)
	// that is the same size
	BITMAPINFO bmi;
	memset(&bmi, 0, sizeof(bmi));
	bmi.bmiHeader.biHeight = bm.bmHeight;
	bmi.bmiHeader.biWidth = bm.bmWidth; 
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biBitCount = 24;
	bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
	int ls = (bmi.bmiHeader.biWidth + 3) & (~3);
	bmi.bmiHeader.biSizeImage = ls * bmi.bmiHeader.biHeight * bmi.bmiHeader.biBitCount / 8;
	bmi.bmiHeader.biXPelsPerMeter = 3780;
	bmi.bmiHeader.biYPelsPerMeter = 3780;

	void *pBuffer;
	HBITMAP hTargetBitmap = CreateDIBSection(hdcSource, &bmi, DIB_RGB_COLORS, (void**)&pBuffer, NULL, 0);

	// Copy the source bitmap into the DIB.
	HDC hdcDest = CreateCompatibleDC(hdcSource);
	HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcDest, hTargetBitmap);
	BitBlt(hdcDest, 0, 0, bm.bmWidth, bm.bmHeight, hdcSource, 0, 0, SRCCOPY);
	SelectObject(hdcDest, hOldBitmap);
	DeleteDC(hdcDest);

	// Finally save the bits to disk
	HANDLE hFile = CreateFile(pszFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile != INVALID_HANDLE_VALUE)
	{
		BITMAPFILEHEADER hdr;
		hdr.bfType		= ((WORD) ('M' << 8) | 'B');
		hdr.bfSize		= sizeof(hdr) + sizeof(BITMAPINFOHEADER);
		hdr.bfReserved1 = 0;
		hdr.bfReserved2 = 0;
		hdr.bfOffBits	= hdr.bfSize;
		
		// Write the BITMAPFILEHEADER and BITMAPINFOHEADER
		// structures followed by the raw pixel data
		DWORD dwByteCount;
		WriteFile(hFile, &hdr, sizeof(hdr), &dwByteCount, NULL);
		WriteFile(hFile, &bmi.bmiHeader, sizeof(bmi.bmiHeader), &dwByteCount, NULL);
		WriteFile(hFile, pBuffer, bmi.bmiHeader.biSizeImage, &dwByteCount, NULL);

		CloseHandle(hFile);
	}

	// Free the bitmap resource
	DeleteObject(hTargetBitmap);
}

// Load the image stored in 'pszFilename' into the bitmap
static void LoadBitmap(LPCTSTR pszFilename, HDC hdcDest, HBITMAP hbmDest)
{
	BITMAP bmSrc, bmDest;

	// Load the image
	HBITMAP hbmSrc = SHLoadImageFile(pszFilename);

	// Figure out the dimensions of the source image
	// and the destination offscreen bitmap buffer
	GetObject(hbmSrc, sizeof(bmSrc), &bmSrc);
	GetObject(hbmDest, sizeof(bmDest), &bmDest);

	// Associate the source bitmap with a device context
	HDC hdcSrc = CreateCompatibleDC(hdcDest);
	HBITMAP hbmOld = (HBITMAP)SelectObject(hdcSrc, hbmSrc);

	// Draw the bitmap onto the offscreen bitmap buffer
	// stretching it to cover the entire area even if
	// this distorts the image.
	StretchBlt(hdcDest, 0, 0, bmDest.bmWidth, bmDest.bmHeight, hdcSrc, 0, 0, bmSrc.bmWidth, bmSrc.bmHeight, SRCCOPY);

	// Tidy up our resources
	SelectObject(hdcSrc, hbmOld);
	DeleteDC(hdcSrc);

	DeleteObject(hbmSrc);
}

static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// Fetch the current state of the signature control
	SignatureState *pState = (SignatureState *)GetWindowLong(hWnd, 0);

	switch (message) 
    {
		case WM_CREATE:
			{
				// Allocate some memory to store the per instance
				// signature control state and initialise it with
				// sensible defaults
				pState = (SignatureState *)malloc(sizeof(SignatureState));
				SetWindowLong(hWnd, 0, (LONG)pState);

				pState->colour = RGB(0, 0, 255);
				pState->width = 1;
				pState->pt.x = pState->pt.y = -1;

				// Create an offscreen bitmap (and associated device
				// context) to store the signature
				HDC temp = GetDC(NULL);
				pState->hdc = CreateCompatibleDC(temp);
				
				RECT rcBounds;
				GetClientRect(hWnd, &rcBounds);
				pState->hbmp = CreateCompatibleBitmap(temp, rcBounds.right - rcBounds.left, rcBounds.bottom - rcBounds.top);

				ReleaseDC(NULL, temp);

				SelectObject(pState->hdc, pState->hbmp);

				// Fill the entire signature bitmap with White
				HBRUSH hbr = CreateSolidBrush(RGB(255,255,255));
				FillRect(pState->hdc, &rcBounds, hbr);
				DeleteObject(hbr);
			}
			break;

		case WM_DESTROY:
			if (pState)
			{
				// Free the offscreen bitmap and device context
				if (pState->hdc)
				{
					DeleteDC(pState->hdc);
					pState->hdc = NULL;
				}
				if (pState->hbmp)
				{
					DeleteObject(pState->hbmp);
					pState->hbmp = NULL;
				}

				// Then free the per instance state structure
				free(pState);
				SetWindowLong(hWnd, 0, NULL);
			}
			break;

		case WM_ERASEBKGND:
			return 1;

		case WM_PAINT:
			{
				// Copy the region of the screen which has been
				// invalidated from the offscreen bitmap
				PAINTSTRUCT ps;
				HDC temp = BeginPaint(hWnd, &ps);
				BitBlt(temp, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, pState->hdc, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY);
				EndPaint(hWnd, &ps);
			}
			return 1;

		case WM_LBUTTONUP:
			pState->pt.x = pState->pt.y = -1;
			break;

		case WM_MOUSEMOVE:
			if (pState->pt.x != -1)
			{
				// This is the second move of the stylus so
				// draw a line between the current and previous
				// points
				HPEN hPen = CreatePen(PS_SOLID, pState->width, pState->colour);
				HPEN hOldPen = (HPEN)SelectObject(pState->hdc, hPen);

				MoveToEx(pState->hdc, pState->pt.x, pState->pt.y, NULL);
				LineTo(pState->hdc, LOWORD(lParam), HIWORD(lParam));

				SelectObject(pState->hdc, hOldPen);
				DeleteObject(hPen);

				// And invalidate the control so it repaints
				InvalidateRect(hWnd, NULL, TRUE);
			}

			// Record the point the stylus was last at
			pState->pt.x = LOWORD(lParam);
			pState->pt.y = HIWORD(lParam);
			break;

		case SM_SETCOLOUR:
			pState->colour = lParam;
			break;

		case SM_GETCOLOUR:
			return pState->colour;

		case SM_SETWIDTH:
			pState->width = lParam;
			break;

		case SM_GETWIDTH:
			return pState->width;

		case SM_CLEAR:
			{
				RECT rcBounds;
				GetClientRect(hWnd, &rcBounds);

				// Clear the offscreen bitmap
				HBRUSH hbr = CreateSolidBrush(RGB(255,255,255));
				FillRect(pState->hdc, &rcBounds, hbr);
				DeleteObject(hbr);

				// And invalidate the control so it repaints
				InvalidateRect(hWnd, NULL, TRUE);
			}
			break;

		case SM_SAVE:
			SaveBitmap((LPCTSTR)lParam, pState->hdc, pState->hbmp);
			break;

		case SM_LOAD:
			LoadBitmap((LPCTSTR)lParam, pState->hdc, pState->hbmp);
			break;

		default: 
			return DefWindowProc(hWnd, message, wParam, lParam); 
	}

	return 0;
}

int InitSignatureControl()
{
	WNDCLASS wc;

	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc   = WndProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = sizeof(SignatureState *);
	wc.hInstance     = GetModuleHandle(NULL);
	wc.hIcon         = NULL;
	wc.hCursor       = 0;
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = 0;
	wc.lpszClassName = SIGNATURE_CLASS;

	return RegisterClass(&wc);
}